﻿// <copyright file="Shape.cs" company="ThorIndustry">Copyright (c) ThorIndustry. All rights reserved.</copyright>

using System.Collections.Generic;

public partial class Shape : UnityEngine.MonoBehaviour
{
    public UnityEngine.Material ShapeMaterial;
    public float SelectionDisplacement = 0.1f;

    private UnityEngine.MeshFilter meshFilter;
    private UnityEngine.MeshRenderer meshRenderer;
    private UnityEngine.PolygonCollider2D polygonCollider2D;
    private UnityEngine.Transform myTransform;

    private UnityEngine.Vector3 center;
    
    public bool CanMove
    {
        get;
        set;
    }

    public bool Flipped
    {
        get;
        set;
    }

    public float HalfWidthScaled
    {
        get
        {
            return this.ShapeWidth / 2 * this.myTransform.localScale.x;
        }
    }

    public float HalfHeightScaled
    {
        get
        {
            return this.ShapeHeight / 2 * this.myTransform.localScale.y;
        }
    }

    public PuzzleShapePosition IdealPuzzleShapePosition
    {
        get;
        set;
    }

    public UnityEngine.Vector2 MaxPoint
    {
        get;
        set;
    }

    public UnityEngine.Vector2 MinPoint
    {
        get;
        set;
    }

    public PuzzleShape PuzzleShape
    {
        get;
        set;
    }

    public bool Selected
    {
        get;
        set;
    }

    public int SelectionIndex
    {
        get;
        set;
    }

    public UnityEngine.Color ShapeColor
    {
        get;
        set;
    }

    public float ShapeWidth
    {
        get;
        set;
    }

    public float ShapeHeight
    {
        get;
        set;
    }

    public bool IsInTheShape(UnityEngine.Vector3 position)
    {
        return this.polygonCollider2D.OverlapPoint(new UnityEngine.Vector2(position.x, position.y));
    }
    
    public void FlipVertically(UnityEngine.Vector3 mousePosition)
    {
        this.myTransform.RotateAround(mousePosition, UnityEngine.Vector3.up, 180);
        this.myTransform.position = new UnityEngine.Vector3(this.myTransform.position.x, this.myTransform.position.y, -this.GetHeight());

        this.Flipped = !this.Flipped;
    }

    public UnityEngine.Vector3 GetCenter()
    {
        return (UnityEngine.Vector3)(this.myTransform.localToWorldMatrix * this.center) + this.myTransform.position;
    }

    public UnityEngine.Vector3 GetLocalCenter()
    {
        return (UnityEngine.Vector3)(this.myTransform.localToWorldMatrix * this.center);
    }

    public virtual void Initialize()
    {
        if (this.CanMove)
        {
            this.meshFilter.mesh = this.CreateExtrudedMesh();
        }
        else
        {
            this.meshFilter.mesh = this.CreateExtrudedMesh();
        }

        this.polygonCollider2D.points = this.PuzzleShape.Points;

        // Apply the color to the material.
        this.meshRenderer.material = this.ShapeMaterial;
        this.meshRenderer.material.color = this.ShapeColor;

        this.Snap();
        UnityEngine.Debug.Log("Shape start");
    }

    public UnityEngine.Vector3 LimitPosition(UnityEngine.Vector3 newPosition)
    {
        // Compute the futur center position.
        UnityEngine.Vector3 centerPosition = this.GetLocalCenter() + newPosition;

        // Compute the screen position.
        UnityEngine.Vector3 screenPosition = UnityEngine.Camera.main.WorldToScreenPoint(centerPosition);
        if (screenPosition.x < 0)
        {
            screenPosition.x = 0;
        }
        else if (screenPosition.x > UnityEngine.Screen.width)
        {
            screenPosition.x = UnityEngine.Screen.width;
        }

        if (screenPosition.y < 0)
        {
            screenPosition.y = 0;
        }
        else if (screenPosition.y > UnityEngine.Screen.height)
        {
            screenPosition.y = UnityEngine.Screen.height;
        }

        UnityEngine.Vector3 newCenterPosition = UnityEngine.Camera.main.ScreenToWorldPoint(screenPosition);

        return newCenterPosition - this.GetLocalCenter();
    }

    public void RefreshZ()
    {
        if (this.CanMove)
        {
            UnityEngine.Vector3 position = this.myTransform.position;
            position.z = -this.GetHeight();

            this.myTransform.position = position;
        }
    }

    public void Snap(SolutionShape solution = null)
    {
        if (!this.CanMove)
        {
            return;
        }

        // When the solution is not null and at least two points of the shape are close enough to solution points, snap on the solution...
        float angle;
        UnityEngine.Vector3 position = this.myTransform.position;
        if (solution != null)
        {
            this.PuzzleShape.ChangePosition(this.myTransform.position, this.myTransform.localEulerAngles.z, this.Flipped);

            // Find two points.
            int[] myIndexes = new int[] {-1, -1};
            int[] otherIndexes = new int[2];
            int pointIndex = 0;
            float[] bestDistance = new float[] {float.MaxValue, float.MaxValue};
            for (int myIndex = 0; pointIndex < myIndexes.Length && myIndex < this.PuzzleShape.WorldVertices.Length; ++myIndex)
            {
                if (bestDistance[0] > bestDistance[1])
                {
                    pointIndex = 0;
                }
                else
                {
                    pointIndex = 1;
                }

                for (int otherIndex = 0; otherIndex < solution.PuzzleSolutionShape.WorldVertices.Length; ++otherIndex)
                {
                    float distance = UnityEngine.Mathf.Pow((this.PuzzleShape.WorldVertices[myIndex].x - solution.PuzzleSolutionShape.WorldVertices[otherIndex].x), 2) +
                                     UnityEngine.Mathf.Pow((this.PuzzleShape.WorldVertices[myIndex].y - solution.PuzzleSolutionShape.WorldVertices[otherIndex].y), 2);

                    if (distance < 1 &&
                        distance < bestDistance[pointIndex])
                    {
                        // fair enough.
                        myIndexes[pointIndex] = myIndex;
                        otherIndexes[pointIndex] = otherIndex;
                        bestDistance[pointIndex] = distance;
                    }
                }
            }

            if (myIndexes[0] != -1 &&
                myIndexes[1] != -1)
            {
                // Verify that the distance between points are correct.
                float distance = UnityEngine.Mathf.Pow((this.PuzzleShape.WorldVertices[myIndexes[0]].x - this.PuzzleShape.WorldVertices[myIndexes[1]].x), 2) +
                                 UnityEngine.Mathf.Pow((this.PuzzleShape.WorldVertices[myIndexes[0]].y - this.PuzzleShape.WorldVertices[myIndexes[1]].y), 2);

                float otherDistance = UnityEngine.Mathf.Pow((solution.PuzzleSolutionShape.WorldVertices[otherIndexes[0]].x - solution.PuzzleSolutionShape.WorldVertices[otherIndexes[1]].x), 2) +
                                      UnityEngine.Mathf.Pow((solution.PuzzleSolutionShape.WorldVertices[otherIndexes[0]].y - solution.PuzzleSolutionShape.WorldVertices[otherIndexes[1]].y), 2);

                if (UnityEngine.Mathf.Abs(distance - otherDistance) < 0.2f)
                {
                    // We found two points.
                    // Now, the thing is, we have to compute the position and the orientation of the shape.
                    // The shape orientation is based on the difference between the solution points vector and the shape points vector.
                    UnityEngine.Vector3 myVector = this.PuzzleShape.WorldVertices[myIndexes[0]] - this.PuzzleShape.WorldVertices[myIndexes[1]];
                    UnityEngine.Vector3 otherVector = solution.PuzzleSolutionShape.WorldVertices[otherIndexes[0]] - solution.PuzzleSolutionShape.WorldVertices[otherIndexes[1]];

                    UnityEngine.Vector3 myPointToPivot = this.PuzzleShape.WorldVertices[myIndexes[0]] - this.myTransform.position;
                    myPointToPivot.z = 0;
                    float distanceBetweenMyPointAndPivot = myPointToPivot.magnitude;

                    myPointToPivot.Normalize();

                    // The angle between the two vector, should be the rotation gap.
                    angle = UnityEngine.Vector3.Angle(myVector, otherVector);

                    UnityEngine.Vector3 cross = UnityEngine.Vector3.Cross(myVector, otherVector);
                    
                    if (cross.z < 0)
                    {
                        angle = -angle;
                    }

                    UnityEngine.Quaternion quaternion = UnityEngine.Quaternion.AngleAxis(angle, UnityEngine.Vector3.forward);

                    // Rotate the point to pivot vector.
                    myPointToPivot = quaternion * myPointToPivot;
                    myPointToPivot.z = 0;
                    myPointToPivot.Normalize();

                    // Rotate the shape.
                    if (this.Flipped)
                    {
                        // The rotation is reversed when the shape is flipped.
                        quaternion = UnityEngine.Quaternion.AngleAxis(angle, -UnityEngine.Vector3.forward);
                    }

                    this.myTransform.rotation *= quaternion;

                    // The position is simply the vector between one point and the "pivot" applied to the solution point.
                    position = solution.PuzzleSolutionShape.WorldVertices[otherIndexes[0]] - (myPointToPivot * distanceBetweenMyPointAndPivot);
                    position.z = -this.GetHeight();
                    this.myTransform.position = position;

                    this.PuzzleShape.ChangePosition(this.myTransform.position, this.myTransform.localEulerAngles.z, this.Flipped);
                    return;
                }
            }
        }
        
        // Snap rotation... 
        angle = 15 * UnityEngine.Mathf.Round(this.myTransform.localEulerAngles.z / 15);
        this.myTransform.localEulerAngles = new UnityEngine.Vector3(0, this.myTransform.localEulerAngles.y, angle);

        // Center cannot goes out of the screen.
        position = this.LimitPosition(position);
        
        // Snap the origin position to be on the grid.
        position.x = UnityEngine.Mathf.Round(position.x);
        position.y = UnityEngine.Mathf.Round(position.y);
        position.z = -this.GetHeight();

        this.myTransform.position = position;

        // Avoid snapping out of the viewport.
        UnityEngine.Vector3 screenPosition = UnityEngine.Camera.main.WorldToScreenPoint(this.GetCenter());
        if (screenPosition.x < 0)
        {
            position.x += 1;
        }
        else if (screenPosition.x > UnityEngine.Screen.width)
        {
            position.x -= 1;
        }

        if (screenPosition.y < 0)
        {
            position.y += 1;
        }
        else if (screenPosition.y > UnityEngine.Screen.height)
        {
            position.y -= 1;
        }

        this.myTransform.position = position;
        
        // Force position to the gameplay.
        this.PuzzleShape.ChangePosition(position, angle, this.Flipped);
    }

    protected virtual void Awake()
    {
        this.CanMove = true;

        // As the transform accessor use the "GetComponent" method, cache the result to avoid too much lookup.
        this.myTransform = this.transform;

        this.meshFilter = (UnityEngine.MeshFilter)this.gameObject.AddComponent(typeof(UnityEngine.MeshFilter));
        this.meshRenderer = this.gameObject.AddComponent(typeof(UnityEngine.MeshRenderer)) as UnityEngine.MeshRenderer;
        this.polygonCollider2D = this.gameObject.AddComponent(typeof(UnityEngine.PolygonCollider2D)) as UnityEngine.PolygonCollider2D;
    }

    ////protected virtual void OnDrawGizmos()
    ////{
    ////    if (!this.CanMove)
    ////    {
    ////        for (int index = 0; index < this.allVertices.Length; ++index)
    ////        {
    ////            UnityEngine.Gizmos.DrawSphere(this.transform.position + this.allVertices[index], 0.02f);
    ////        }
    ////    }
    ////}
    
    private UnityEngine.Mesh CreateShapeMesh()
    {
        UnityEngine.Mesh mesh = new UnityEngine.Mesh();
        mesh.name = "ScriptedMesh";

        mesh.vertices = this.PuzzleShape.Vertices;
        mesh.triangles = this.PuzzleShape.Triangles;
        UnityEngine.Vector2[] uvs = new UnityEngine.Vector2[mesh.vertices.Length];

        float minX = this.PuzzleShape.Vertices[0].x;
        float maxX = this.PuzzleShape.Vertices[0].x;
        float minY = this.PuzzleShape.Vertices[0].y;
        float maxY = this.PuzzleShape.Vertices[0].y;

        for (int i = 1; i < this.PuzzleShape.Vertices.Length; i++)
        {
            UnityEngine.Vector3 vertice = this.PuzzleShape.Vertices[i];
            minX = UnityEngine.Mathf.Min(vertice.x, minX);
            maxX = UnityEngine.Mathf.Max(vertice.x, maxX);
            minY = UnityEngine.Mathf.Min(vertice.y, minY);
            maxY = UnityEngine.Mathf.Max(vertice.y, maxY);
        }

        this.MaxPoint = new UnityEngine.Vector2(maxX, maxY);
        this.MinPoint = new UnityEngine.Vector2(minX, minY);
        
        this.ShapeWidth = maxX - minX;
        this.ShapeHeight = maxY - minY;

        this.center.x = maxX - (this.ShapeWidth / 2);
        this.center.y = maxY - (this.ShapeHeight / 2);
        
        for (int index = 0; index < mesh.vertices.Length; index++)
        {
            uvs[index] = new UnityEngine.Vector2((mesh.vertices[index].x - minX) / (maxX - minX), (mesh.vertices[index].y - minY) / (maxY - minY));
        }

        mesh.uv = uvs;

        mesh.RecalculateNormals();

        return mesh;
    }

    private float GetHeight()
    {
        return this.SelectionDisplacement * this.SelectionIndex;
    }
}
